踏み台サーバ経由のSSHセッションを記録する方法
こんにちは。大阪の市田です。
今回は、下記のブログの内容を元に、踏み台サーバ経由のSSHセッションを記録する方法をご紹介します。
How to Record SSH Sessions Established Through a Bastion Host | AWS Security Blog
尚、踏み台サーバはAmazon Linuxを想定しています。
ポイント
この記事のポイントは下記です。
- OpenSSHの設定の修正
- scriptコマンドの利用
- 踏み台サーバユーザの権限制限
- ログファイルのS3保管
- S3による踏み台サーバユーザの自動管理
- SSHのエージェントフォワード利用
- CloudFormationで環境構築
それでは順に説明していきたいと思います。
構成
想定の構成は下記の通りです。
ログファイルのディレクトリ作成
まずは、踏み台サーバにログの保存ディレクトリを作成し、アクセス制限を行います。
# mkdir /var/log/bastion # chown ec2-user:ec2-user /var/log/bastion # chmod -R 770 /var/log/bastion # setfacl -Rdm other:0 /var/log/bastion
OpenSSHの設定
SSHログイン時に/usr/bin/bastion/shell
というスクリプトを実行するようにします。スクリプトの内容は後で記載します。
# echo -e "\nForceCommand /usr/bin/bastion/shell " >> /etc/ssh⁄sshd_config
次に、今回の設定を回避できないように一部のSSHの機能を無効化します。
# awk '!/AllowTcpForwarding/' /etc/ssh⁄sshd_config> temp && mv temp /etc/ssh⁄sshd_config # awk '!/X11Forwarding/' /etc/ssh⁄sshd_config> temp && mv temp /etc/ssh⁄sshd_config # echo "AllowTcpForwarding no" >> /etc/ssh⁄sshd_config # echo "X11Forwarding no" >> /etc/ssh⁄sshd_config
ログイン時のスクリプト作成
先程の/usr/bin/bastion/shell
を作成します。まずはディレクトリを作成します。
# mkdir /usr/bin/bastion
下記の内容で/usr/bin/bastion/shell
を作成します。
# Check that the SSH client did not supply a command if [[ -z $SSH_ORIGINAL_COMMAND ]]; then # The format of log files is /var/log/bastion/YYYY-MM-DD_HH-MM-SS_user LOG_FILE="`date --date="today" "+%Y-%m-%d_%H-%M-%S"`_`whoami`" LOG_DIR="/var/log/bastion/" # Print a welcome message echo "" echo "NOTE: This SSH session will be recorded" echo "AUDIT KEY: $LOG_FILE" echo "" # I suffix the log file name with a random string. I explain why later on. SUFFIX=`mktemp -u _XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX` # Wrap an interactive shell into "script" to record the SSH session script -qf --timing=$LOG_DIR$LOG_FILE$SUFFIX.time $LOG_DIR$LOG_FILE$SUFFIX.data --command=/bin/bash else # The "script" program could be circumvented with some commands (e.g. bash, nc). # Therefore, I intentionally prevent users from supplying commands. echo "This bastion supports interactive sessions only. Do not supply a command" exit 1 fi
このスクリプトは、環境変数$SSH_ORIGINAL_COMMAND
の有無によって処理が変わります。
もしこの環境変数がセットされていなければ、ログイン時にscriptコマンドの中にインタラクティブシェルがラップされます。
環境変数$SSH_ORIGINAL_COMMAND
がセットされている場合は、下記メッセージが出力されてログイン出来ません。例えば、ec2-userの公開鍵(~/.ssh/authorized_keys)を下記のように編集します。
command="grep $SSH_ORIGINAL_COMMAND /var/log/cloud-init.log" ssh-rsa AAA (以下略)
この状態で、ローカルPCからコマンドを実行してみます。
$ ssh -i [path to bastion.pem] ec2-user@[public IP of the bastion host] "GET" This bastion supports interactive sessions only. Do not supply a command
インタラクティブなセッションしか受け付けないというメッセージが出て、期待するコマンドを実行出来ませんでした。
上記スクリプトのコメントにも記載されていますが、scriptコマンドによる記録は、bash
やnc
などで回避することができてしまいます。これを防止する為に$SSH_ORIGINAL_COMMAND
のチェックを行っているという訳です。
作成できたら実行権限を付けておきます。
# chmod a+x /usr/bin/bastion/shell
その他に、踏み台サーバ上で他ユーザのプロセスが見えないようにするといった制限を設定します。
# chown root:ec2-user /usr/bin/script # chmod g+s /usr/bin/script # mount -o remount,rw,hidepid=2 /proc # awk '!/proc/' /etc/fstab > temp && mv temp /etc/fstab # echo "proc /proc proc defaults,hidepid=2 0 0" >> /etc/fstab
ここまでできたら、設定を読み込むためにSSHサービスを再起動します。
# service sshd restart
ログファイルのS3保管
上記のスクリプトにある通り、ログイン後のSSHセッションのログファイルは/var/log/bastion
ディレクトリに保存されます。また、このログはS3バケットに定期的にコピーしておくようにします。
下記のような内容のスクリプト(/usr/bin/bastion/sync_s3
)を作成します。
# Copy log files to S3 with server-side encryption enabled. # Then, if successful, delete log files that are older than a day. LOG_DIR="/var/log/bastion/" aws s3 cp $LOG_DIR s3://bucket-name/logs/ --sse --region region --recursive && find $LOG_DIR* -mtime +1 -exec rm {} \;
実行権限を付けます。
# chmod 700 /usr/bin/bastion/sync_s3
S3による踏み台サーバユーザの自動管理
この記事では、踏み台サーバのアカウント管理にS3を利用する方法を紹介しています。仕組みの概要は次の通りです。
- 踏み台サーバから接続するサーバのキーペアを作成(秘密鍵のダウンロード)
- 秘密鍵から公開鍵を作成
- アカウント毎の公開鍵をS3にアップロード
- 踏み台サーバがS3上の公開鍵に対するユーザを自動作成
- S3上の公開鍵が削除されると踏み台サーバ上のユーザも削除
- 踏み台サーバはcronでS3バケットを定期チェック
踏み台サーバのユーザを作成、削除したログは下記のような内容で/var/log/bastion/users_changelog.txt
に保存されます。
2017-03-06 07-55-02: Creating user account for sshuser (public-keys/sshuser.pub) 2017-03-06 09-00-03: Removing user account for sshuser (public-keys/sshuser.pub)
それでは、S3を定期的にチェックしてアカウントを管理するスクリプト(/usr/bin/bastion/sync_users
)を作成します。
# Bastion host users should log in to the bastion host with # their personal SSH key pair. The public keys are stored on # S3 with the following naming convention: "username.pub". This # script retrieves the public keys, creates or deletes local user # accounts as needed, and copies the public key to # /home/username/.ssh/authorized_keys cat > /usr/bin/bastion/sync_users << 'EOF' # The file will log user changes LOG_FILE="/var/log/bastion/users_changelog.txt" # The function returns the user name from the public key file name. # Example: public-keys/sshuser.pub => sshuser get_user_name () { echo "$1" | sed -e 's/.*\///g' | sed -e 's/\.pub//g' } # For each public key available in the S3 bucket aws s3api list-objects --bucket bucket-name --prefix public-keys/ --region region --output text --query 'Contents[?Size>`0`].Key' | sed -e 'y/\t/\n/' > ~/keys_retrieved_from_s3 while read line; do USER_NAME="`get_user_name "$line"`" # Make sure the user name is alphanumeric if [[ "$USER_NAME" =~ ^[a-z][-a-z0-9]*$ ]]; then # Create a user account if it does not already exist cut -d: -f1 /etc/passwd | grep -qx $USER_NAME if [ $? -eq 1 ]; then /usr/sbin/adduser $USER_NAME && \ mkdir -m 700 /home/$USER_NAME/.ssh && \ chown $USER_NAME:$USER_NAME /home/$USER_NAME/.ssh && \ echo "$line" >> ~/keys_installed && \ echo "`date --date="today" "+%Y-%m-%d %H-%M-%S"`: Creating user account for $USER_NAME ($line)" >> $LOG_FILE fi # Copy the public key from S3, if a user account was created # from this key if [ -f ~/keys_installed ]; then grep -qx "$line" ~/keys_installed if [ $? -eq 0 ]; then aws s3 cp s3://bucket-name/$line /home/$USER_NAME/.ssh/authorized_keys --region region chmod 600 /home/$USER_NAME/.ssh/authorized_keys chown $USER_NAME:$USER_NAME /home/$USER_NAME/.ssh/authorized_keys fi fi fi done < ~/keys_retrieved_from_s3 # Remove user accounts whose public key was deleted from S3 if [ -f ~/keys_installed ]; then sort -uo ~/keys_installed ~/keys_installed sort -uo ~/keys_retrieved_from_s3 ~/keys_retrieved_from_s3 comm -13 ~/keys_retrieved_from_s3 ~/keys_installed | sed "s/\t//g" > ~/keys_to_remove while read line; do USER_NAME="`get_user_name "$line"`" echo "`date --date="today" "+%Y-%m-%d %H-%M-%S"`: Removing user account for $USER_NAME ($line)" >> $LOG_FILE /usr/sbin/userdel -r -f $USER_NAME done < ~/keys_to_remove comm -3 ~/keys_installed ~/keys_to_remove | sed "s/\t//g" > ~/tmp && mv ~/tmp ~/keys_installed fi
実行権限を付けます。
chmod 700 /usr/bin/bastion/sync_users
cronに登録します。
# crontab -e # 下記の内容で登録します */5 * * * * /usr/bin/bastion/sync_s3 */5 * * * * /usr/bin/bastion/sync_users 0 0 * * * yum -y update --security
尚、鍵ファイルの取扱いには十分に注意するようにしましょう。S3バケットへのアクセスもバケットポリシーやIAMポリシーを使って、IP制限や部門毎に権限を分けるなど、適切に設定することが望ましいです。
CloudFormationでテスト環境を構築
これまで説明した構成はCloudFormationですぐに試すことができます。 その前に、キーペアを2つ作成しておきます。秘密鍵は忘れずダウンロードしましょう。
- bastion
- 踏み台サーバ用のキーペアです。
- sshuser
- 踏み台からアクセスするプライベートサブネットに置くサーバのキーペアです。
CloudFormationのスタック作成は下記リンクから可能です。
Create a Stack - Record SSH Sessions Bastion Host
作成するリージョンを確認します。
Stack nameは適宜変更して下さい。先程作成した2つのキーペアをそれぞれパラメータで指定します。
CloudFormationで作成されるもの
- インターネットゲートウェイがアタッチされたVPC
- パブリックサブネット
- プライベートサブネット(インターネットアクセス不可)
- sshbastionrecording-bucket-xxxxxxxxxxというS3バケット
- ログは
logs
フォルダに保存されます。 - アカウント管理用の公開鍵は
public-keys
フォルダに保存されますが、これは手動で作成します。
- ログは
- 2つのセキュリティグループ
- 1つは踏み台サーバ用で、インターネットからのSSHアクセスを許可します
- 0.0.0.0/0で公開されるので、適宜修正して下さい。
- 2つ目は踏み台サーバ用のセキュリティグループからのSSHアクセスを許可します。
- S3に対するいくつかの権限を持ったIAMロール
- 踏み台サーバにひも付きます。
- 踏み台サーバと踏み台サーバからアクセスされるプライベート上のサーバ
- いずれもAmazon Linuxです。
作成されるS3バケット名や各サーバのIPアドレスは、CloudFormationのOutputタブで確認できます。
動作確認
公開鍵の取得
それでは、実際に動作確認してみます。
まず、プライベートサブネットにあるサーバの公開鍵をS3にアップロードします。
とはいえ、手元にはsshuser.pem
という秘密鍵しか無いので、秘密鍵から公開鍵を取得します。
最初に秘密鍵のパーミッションを修正します。
$ chmod 400 sshuser.pem
秘密鍵があるPC上でssh-keygen
コマンドで公開鍵を取得します。
$ ssh-keygen -y
秘密鍵の入力を求められるので、秘密鍵のパスを入力します。
Enter file in which the key is (/Users/xxxxxx/.ssh/id_rsa):[path to sshuser.pem]
下記のように公開鍵の内容が返ってきますので、公開鍵ファイル(sshuser.pub
)として保存します。
ssh-rsa AAA(以下略)
参考: Amazon EC2 のキーペア - Amazon Elastic Compute Cloud
公開鍵のアップロード
CloudFormationで作成されたS3バケットに対して、手動でpublic-keys
というフォルダを作成します。そのフォルダにsshuser.pub
を保存します。
数分待てば、踏み台サーバのcronによりsshuser
アカウントが作成されます。逆に保存したpubファイルを削除すると、踏み台サーバからも該当ユーザが削除されます。
踏み台サーバにログイン
それでは踏み台サーバにログインします。しかし、プライベートサブネットにあるサーバの秘密鍵を踏み台サーバに置きたくありませんので、SSHエージェントフォワーディングを使います。
$ chmod 400 [path to sshuser.pem] $ ssh-add [path to sshuser.pem] Identity added: [path to sshuser.pem] ([path to sshuser.pem]) # 確認(記憶した鍵の一覧表示) $ ssh-add -l 2048 SHA256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx [path to sshuser.pem] (RSA)
それでは踏み台サーバにログインします。ログインすると「AUDIT KEY」 が表示されます。これが該当のSSHセッションを記録しているIDになります。
$ ssh -A sshuser@[public IP of the bastion host] -i [path to sshuser.pem] NOTE: This SSH session will be recorded AUDIT KEY: 2017-03-03_17-16-39_sshuser
踏み台サーバにログインできたら、そこからプライベートサブネットにあるサーバにログインします。ログインする時は秘密鍵の指定なくログイン可能です。
$ ssh ec2-user@[private IP of the Linux instance]
ログアウトした後は、安全の為に記憶した鍵情報は削除しておきます。-D
オプションで記憶したものを全て削除できます。
$ ssh-add -D
SSHセッションのリプレイ
踏み台サーバでは/usr/bin/bastion/sync_s3
により、scriptコマンドによるログが1日でサーバから削除されます。その為、リプレイする場合は通常、該当するファイルをS3からダウンロードしてリプレイするようにします。
例えば、S3のReadOnlyのIAM権限を持ったEC2インスタンスで再生します。(IAMの権限は必要最小限にします。)
リプレイ(再生)
まずはS3から対象のファイルをダウンロードします。今回はAWS CLIを使っています。「AUDIT KEY」は調査したい日時のものを指定して下さい。
$ aws s3 cp --profile ichida-s3-readonly s3://sshbastionrecording-bucket-xxxxxxxxxx/logs/ . --recursive --exclude "*" --include "[AUDIT KEY]*" $ export LOGFILE=`ls [AUDIT KEY]*.data | cut -d. -f1`
ファイルを指定して再生します。
$ scriptreplay --timing=$LOGFILE.time $LOGFILE.data
CloudFormationスタックの削除
CloudFormationで作成した各リソースは、スタックの削除ですべて消せますが、S3バケットにデータがあると削除できませんので、バケットを空にしておくことに注意して下さい。
最後に
この内容は監査等にも利用できるかと思いますので、うまく組み合わせて頂ければと思います。
以上です。